﻿using System;
using System.IO;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Xml.Linq;
using Crm.Utilities.SolutionsDeploymentTool.UI.Helpers;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

namespace Crm.Utilities.SolutionsDeploymentTool.UI
{
    
    public class Connection
    {
        private const string SERVICE_PATH = "XrmServices/2011/Organization.svc";
        private const string TARGETCONFIG_FILE_PATH = "TargetConnection.xml";
        private const string SOURCECONFIG_FILE_PATH = "SourceConnection.xml";
        
        private Guid _organizationId = Guid.Empty;
        private bool _isLoaded = false;

        public Guid OrganizationId { get { return _organizationId; } }
        public bool IsLoaded { get { return _isLoaded; } }
        public string ServerUrl { get; set; }
        public string OrgName { get; set; }
        public string UserDomain { get; set; }
        public string UserLogin { get; set; }
        public string UserPassword { get; set; }
        public string SolutionsFolder { get; set; }
        public ConnectionType ConnectionMode { get; set; }
        public bool IsConnected { get; internal set; }
        public bool IsTarget { get; set; }
        public TimeSpan ServiceTimeout { get; set; }
        public IOrganizationService CrmOrganizationService { get; private set; }
        public SourceType SourceConnectionType { get; set; }
        
        public Connection(string serverUrl, string orgName, string domain, string login, string password, ConnectionType connType, bool isTarget, TimeSpan timeout, string solutionFolder, SourceType sourceConnType, bool saveConfig, bool savePassword = false)
        {
            this.IsConnected = false;
            this.ServerUrl = serverUrl;
            this.OrgName = orgName;
            this.UserLogin = login;
            this.UserPassword = password;
            this.UserDomain = domain;
            this.ConnectionMode = connType;
            this.ServiceTimeout = timeout;
            this.IsTarget = isTarget;
            this.SolutionsFolder = solutionFolder;
            this.SourceConnectionType = sourceConnType; 
            this.CrmOrganizationService = GetOrganizationService();
            
            _isLoaded = true;

            if (saveConfig)
                Save(savePassword);
        }

        public Connection(bool isTarget)
        {
            this.IsConnected = false;
            this.IsTarget = isTarget;
            this.ServiceTimeout = TimeSpan.FromMinutes(5);
            Load();

            CrmOrganizationService = GetOrganizationService();
        }

        public void Save(bool savePassword = false)
        {
            XElement c = new XElement(Constants.XML_Connection);

            if (ServerUrl == null) ServerUrl = string.Empty;
            XElement serverElement = new XElement(Constants.XML_ServerUrl);
            serverElement.SetValue(ServerUrl);
            c.Add(serverElement);

            if (OrgName == null) OrgName = string.Empty;
            XElement orgELement = new XElement(Constants.XML_OrgName);
            orgELement.SetValue(OrgName);
            c.Add(orgELement);

            if (UserLogin == null) UserLogin = string.Empty;
            XElement loginElement = new XElement(Constants.XML_UserLogin);
            loginElement.SetValue(UserLogin);
            c.Add(loginElement);

            if (UserDomain == null) UserDomain = string.Empty;
            XElement domainElement = new XElement(Constants.XML_UserDomain);
            domainElement.SetValue(UserDomain);
            c.Add(domainElement);

            XElement securePassElement = new XElement(Constants.XML_SecurePassword);
            if (savePassword)
            {
                if (UserPassword == null) UserPassword = string.Empty;
                securePassElement.SetValue(Encryption.TripleDESEncrypt(UserPassword));
            }
            else
            {
                securePassElement.SetValue(String.Empty);
            }
            c.Add(securePassElement);

            XElement timeoutElement = new XElement(Constants.XML_Timeout);
            timeoutElement.SetValue(ServiceTimeout.TotalMinutes.ToString());
            c.Add(timeoutElement);

            XElement sourceConnectionType = new XElement(Constants.XML_SourceConnectionType);
            sourceConnectionType.SetValue(SourceConnectionType.ToString());
            c.Add(sourceConnectionType);

            if (SolutionsFolder == null) SolutionsFolder = string.Empty;
            XElement solutionsFolder = new XElement(Constants.XML_SolutionFolder);
            solutionsFolder.SetValue(SolutionsFolder);
            c.Add(solutionsFolder);

            XElement connTypeElement = new XElement(Constants.XML_ConnType);
            connTypeElement.SetValue(ConnectionMode.ToString());
            c.Add(connTypeElement);

            if (IsTarget)
                File.WriteAllText(TARGETCONFIG_FILE_PATH, c.ToString());
            else
                File.WriteAllText(SOURCECONFIG_FILE_PATH, c.ToString());

            _isLoaded = true;
        }

        private void Load()
        {
            string data = string.Empty;

            if (IsTarget && File.Exists(TARGETCONFIG_FILE_PATH))
            {
                data = File.ReadAllText(TARGETCONFIG_FILE_PATH);
            }
            else if (!IsTarget && File.Exists(SOURCECONFIG_FILE_PATH))
            {
                data = File.ReadAllText(SOURCECONFIG_FILE_PATH);
            }

            if (!string.IsNullOrEmpty(data))
            {
                XElement c = XElement.Parse(data);

                var server = c.Element(Constants.XML_ServerUrl);
                if (server != null) ServerUrl = server.Value;

                var org = c.Element(Constants.XML_OrgName);
                if (org != null) OrgName = org.Value;

                var login = c.Element(Constants.XML_UserLogin);
                if (login != null) UserLogin = login.Value;

                var domain = c.Element(Constants.XML_UserDomain);
                if (domain != null) UserDomain = domain.Value;

                var securePass = c.Element(Constants.XML_SecurePassword);
                if (securePass != null && !string.IsNullOrEmpty(securePass.Value)) UserPassword = Encryption.TripleDESDecrypt(securePass.Value);
                else UserPassword = string.Empty;

                var connType = c.Element(Constants.XML_ConnType);
                if (connType != null && !string.IsNullOrEmpty(connType.Value))
                {
                    ConnectionType result = ConnectionType.Default;
                    Enum.TryParse<ConnectionType>(connType.Value, out result);
                    ConnectionMode = result;
                }

                var sourceConnectionType = c.Element(Constants.XML_SourceConnectionType);
                if (sourceConnectionType != null && !string.IsNullOrEmpty(sourceConnectionType.Value))
                {
                    SourceType result = SourceType.CRM;
                    Enum.TryParse<SourceType>(sourceConnectionType.Value, out result);
                    SourceConnectionType = result;
                }

                var timeout = c.Element(Constants.XML_Timeout);
                if (timeout != null)
                {
                    if (string.IsNullOrEmpty(timeout.Value)) ServiceTimeout = TimeSpan.FromMinutes(5);
                    else ServiceTimeout = TimeSpan.FromMinutes(int.Parse(timeout.Value));
                }

                var solutionFolder = c.Element(Constants.XML_SolutionFolder);
                if (solutionFolder != null) SolutionsFolder = solutionFolder.Value;

                _isLoaded = true;
            }
        }

        public void ClearSavedInfo()
        {
            if (File.Exists(SOURCECONFIG_FILE_PATH))
            {
                File.Delete(SOURCECONFIG_FILE_PATH);
            }
            if (File.Exists(TARGETCONFIG_FILE_PATH))
            {
                File.Delete(TARGETCONFIG_FILE_PATH);
            }
        }

        public string TestConnection()
        {
            string message = string.Empty;

            try
            {
                // Reset OrganizationId if it's to be reloaded
                this._organizationId = Guid.Empty;
                
                try
                {
                    IOrganizationService service = GetOrganizationService();
                    this._organizationId = ((WhoAmIResponse)service.Execute(new WhoAmIRequest())).OrganizationId;
                    
                    IsConnected = true;
                    message = Constants.Message_Success;
                }
                catch (System.ServiceModel.Security.MessageSecurityException ex)
                {
                    IsConnected = false;

                    if (ex.InnerException != null) message = string.Format("Security Error : {0}", ex.InnerException.Message);
                    else message = string.Format("Security Error : {0}", ex.Message);
                }
                catch (FaultException<OrganizationServiceFault> ex)
                {
                    IsConnected = false;
                    message = "Error (Org Serivce) : " + ex.Detail.Message;
                }
                catch (Exception ex)
                {
                    IsConnected = false;
                    message = "Error  : " + ex.Message;
                }
            }
            catch (Exception ex)
            {
                message = "Generic Error  : " + ex.Message;
            }

            return message;
        }

        public IOrganizationService GetOrganizationService()
        {
            if (_isLoaded)
            {
                try
                {
                    OrganizationServiceProxy service = null;
                    ClientCredentials creds = new ClientCredentials();

                    if (!ServerUrl.EndsWith("/"))
                    {
                        ServerUrl += "/";
                    }

                    if (ConnectionMode == ConnectionType.CrmOnline)
                    {
                        //CRM Online:
                        ClientCredentials deviceCredentials = Microsoft.Crm.Services.Utility.DeviceIdManager.LoadOrRegisterDevice();
                        creds.UserName.UserName = this.UserLogin;
                        creds.UserName.Password = this.UserPassword;

                        Uri serviceUrl = new Uri(string.Format("{0}{1}", ServerUrl, SERVICE_PATH));
                        service = new Microsoft.Xrm.Sdk.Client.OrganizationServiceProxy(serviceUrl, null, creds, deviceCredentials);
                    }
                    else if (ConnectionMode == ConnectionType.ADFS)
                    {
                        Uri serviceUrl = new System.Uri(string.Format("{0}{1}", ServerUrl, SERVICE_PATH));
                        ClientCredentials credentials = new ClientCredentials();
                        credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
                        credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
                        if (!string.IsNullOrEmpty(UserLogin))
                        {
                            credentials.UserName.UserName = this.UserLogin;
                            credentials.UserName.Password = this.UserPassword;
                        }
                        service = new OrganizationServiceProxy(serviceUrl, null, credentials, null);
                    }
                    else
                    {
                        Uri serviceUrl = new System.Uri(string.Format("{0}{1}/{2}", ServerUrl, OrgName, SERVICE_PATH));

                        if (!string.IsNullOrEmpty(UserLogin))
                        {
                            if (!string.IsNullOrEmpty(UserLogin) && !string.IsNullOrEmpty(UserDomain)) //
                            {
                                //username and domain provided
                                creds.Windows.ClientCredential = new System.Net.NetworkCredential(UserLogin, UserPassword, UserDomain);
                            }
                            else
                            {
                                //username but no domain 
                                creds.Windows.ClientCredential = new System.Net.NetworkCredential(UserLogin, UserPassword);
                            }
                        }
                        else
                        {
                            //no username provided, use defualt credentials
                            creds.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
                        }
                        service = new OrganizationServiceProxy(serviceUrl, null, creds, null);
                    }

                    service.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
                    service.Timeout = ServiceTimeout;
                    return service;
                }
                catch
                {
                    return null;
                }

            }

            return null;
        }

    }
}
